package com.blue.base.shiro.config;

import com.blue.base.shiro.constants.ShiroConfigBean;
import com.blue.base.shiro.filter.RolesAuthFilter;
import com.blue.base.shiro.listener.RedisSessionListener;
import com.blue.base.shiro.realms.AuthRealms;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * shiro初始化
 *
 * @author liulei
 * @version 1.0
 */
@Slf4j
@Configuration
@ConditionalOnProperty(value = {"sys.auth.enabled", "spring.redis.host"})
public class ShiroInitConfigSimple {

    @Autowired
    private ShiroConfigBean configBean;

    public ShiroInitConfigSimple() {
        log.info("==========单机版/一主多从 redis shiro 缓存初始化==========");
    }

    /**
     * 注入SecurityManager，包含权限主体【subject】配置和【realms】
     * 配置分布式redis session
     */
    @Bean
    public SecurityManager securityManager(AuthRealms authRealms) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        authRealms.setCredentialsMatcher(new AllowAllCredentialsMatcher());
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(cacheManager());
        securityManager.setRealm(authRealms);
        return securityManager;
    }

    @Bean
    public SessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        Collection<SessionListener> listeners = new ArrayList<>();
        listeners.add(new RedisSessionListener());
        sessionManager.setSessionListeners(listeners);
        //设置session超时时间(单位毫秒)
        sessionManager.setGlobalSessionTimeout(ShiroConfigBean.EXPIRE_SECONDS * 1000);
        //定时检查session
        sessionManager.setSessionValidationSchedulerEnabled(true);
        //设置sessionDao
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }

    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setKeyPrefix(String.format("%s%s%s", configBean.getAuthKeyPrefix(), "session", ":"));
        // 单位为秒
        redisSessionDAO.setExpire(ShiroConfigBean.EXPIRE_SECONDS);
        return redisSessionDAO;
    }

    /**
     * redis缓存配置【单机/一主多从模式】
     */
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(configBean.getRedisHost());
        // 连接超时
        redisManager.setTimeout(3600);
        return redisManager;
    }

    /**
     * URL过滤器工厂配置
     * 过滤链定义，从上向下顺序执行，一般将 /**放在最为下边;
     * authc:所有url都必须认证通过才可以访问;
     * anon:所有url都都可以匿名访问
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        /* ************* 注册自定义授权过滤器 开始******************/
        Map<String, String> map = new LinkedHashMap<>();
        //配置不拦截的url
        for (String anno : configBean.getAuthSkipUrl()) {
            map.put(anno, "anon");
        }
        //配置退出接口
        map.put("/auth/quit", "authc");
        //一个url对应一个filter，使用系统或者自定义的都可以，此处使用
        map.put("/**", "myfilter");
        shiroFilterFactoryBean.setLoginUrl("/unauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        //修改授权部分不走@RequireRoles注解
        Map<String, Filter> filterMap = Maps.newLinkedHashMap();
        //具体过滤器类型key和value配置：http://shiro.apache.org/web.html#Web-%7B%7B%5Curls%5C%7D%7D
        filterMap.put("myfilter", new RolesAuthFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 凭证匹配器【密码匹配凭证规则】
     */
    @Bean(name = "credentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 散列的次数，比如散列两次，相当于 md5(md5(""));
        hashedCredentialsMatcher.setHashIterations(1);
        // storedCredentialsHexEncoded默认是true，此时用的是密码加密用的是Hex编码；false时用Base64编码
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }

    /**
     * 开启相关注解(如@RequiresRoles,@RequiresPermissions)，借助SpringBoot扫描
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 配置具体cache实现类
     */
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setExpire(7200000);
        //格式：keyPrefix:ShiroAuthRealms.authorizationCache:账户名
        redisCacheManager.setKeyPrefix(configBean.getAuthKeyPrefix());
        return redisCacheManager;
    }

    @Bean
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setUsePrefix(true);
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
}
